/*
 * Copyright 2025-2026 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package hawk.demo;

import glz.hawkgenerator.java.*;
import glz.hawkgenerator.java.type.ArrayTypeName;
import glz.hawkgenerator.java.type.ClassName;
import glz.hawkgenerator.java.type.ParameterizedTypeName;
import hawk.util.ProjectUtils;

import javax.lang.model.element.Modifier;
import java.io.IOException;
import java.util.function.Consumer;

import static glz.hawkgenerator.java.type.PrimitiveTypeName.INT;
import static glz.hawkgenerator.java.type.VoidTypeName.VOID;

/**
 * This class is responsible for
 *
 * @author Hawk
 */
public class ClassGenerator {
    private final String packageName;

    public ClassGenerator(String packageName) {
        this.packageName = packageName;
    }

    public void generateClass() {

        // Mammal annotation
        ProjectUtils.write(packageName,
                AnnotationSpec.builder("Mammal", Modifier.PUBLIC)
                        .build()
        );

        // Mammal annotation
        ProjectUtils.write(packageName,
                AnnotationSpec.builder("Tag", Modifier.PUBLIC)
                        .build()
        );


        // Movable interface
        ProjectUtils.write(packageName,
                InterfaceSpec.builder("Movable", Modifier.PUBLIC)
                        .addMethod(MethodSpec.builder(INT, "getSpeed").build())
                        .addMethod(MethodSpec.builder(String.class, "getMovementType").build())
                        .build()
        );

        // Movable interface
        ProjectUtils.write(packageName,
                InterfaceSpec.builder("Audible", Modifier.PUBLIC)
                        .addMethod(MethodSpec.builder(INT, "getVolumeLevel").build())
                        .addMethod(MethodSpec.builder(String.class, "getSoundType").build())
                        .build()
        );

        // Movable interface
        ProjectUtils.write(packageName,
                ClassSpec.builder("Animal", Modifier.PUBLIC, Modifier.ABSTRACT)
                        .addField(FieldSpec.builder(String.class, "animalType", Modifier.PRIVATE, Modifier.FINAL).build())
                        .addConstructor(ConstructorSpec.builder(Modifier.PUBLIC)
                                .addParameter(String.class, "animalType")
                                .beginConstructorBody()
                                .addStatement("this.animalType = animalType")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(String.class, "getAnimalType", Modifier.PUBLIC)
                                .beginMethodBody()
                                .addStatement("return animalType")
                                .end()
                                .build())
                        .build()
        );

        // cat
        ProjectUtils.write(packageName,
                ClassSpec.builder("Cat", Modifier.PUBLIC)
                        .addAnnotation(AnnotationInstanceSpec.builder(ClassName.of(packageName, "Mammal")).build())
                        .setSuperClass(ClassName.of(packageName, "Animal"))
                        .addSuperInterfaces(ClassName.of(packageName, "Movable"), ClassName.of(packageName, "Audible"))
                        .addConstructor(ConstructorSpec.builder(Modifier.PUBLIC)
                                .beginConstructorBody()
                                .addStatement("super($S)", "CAT")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(INT, "getVolumeLevel", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return 55")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(INT, "getSpeed", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return 12")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(String.class, "getSoundType", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return $S", "meow")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(String.class, "getMovementType", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return $S", "walk")
                                .end()
                                .build())
                        .addInnerType(ClassSpec.builder("LittleCat")
                                        .addField(FieldSpec.builder(String.class, "COLOR", Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
                                                .addAnnotation(AnnotationInstanceSpec.builder(ClassName.of(packageName, "Tag")).build())
                                                .setInitializer("$S", "BLACK AND WHITE")
                                                .build())
                                        .addField(FieldSpec.builder(ParameterizedTypeName.of(Consumer.class, ClassName.of(packageName, "Cat")), "consumer")
                                                .setInitializer("$L", ClassSpec.anonymousBuilder()
                                                        .setSuperClass(ParameterizedTypeName.of(Consumer.class, ClassName.of(packageName, "Cat")))
                                                        .addMethod(MethodSpec.builder(VOID, "accept", Modifier.PUBLIC)
                                                                .addParameter(ClassName.of(packageName, "Cat"), "cat")
                                                                .beginMethodBody()
                                                                .addStatement("System.out.println(cat.getSpeed())")
                                                                .end()
                                                                .build())
                                                        .build())
                                                .build())
                                        .addMethod(MethodSpec.builder(String.class, "favorite", Modifier.PUBLIC)
                                                .addParameter(String.class, "toyType")
                                                .addParameter(ArrayTypeName.ofClass(String.class), "toys")
                                                .varargs()
                                                .beginMethodBody()
                                                .addStatement("return toyType + $S + String.join($S, toys)", ": ", ", ")
                                                .end()
                                                .build())
                                        .addMethod(MethodSpec.builder(VOID, "save", Modifier.PUBLIC)
                                                .addThrowable(IOException.class)
                                                .beginMethodBody()
                                                .addStatement("throw new $T()", IOException.class)
                                                .end()
                                                .build())
                                        .addMethod(MethodSpec.builder(VOID, "playWith", Modifier.PUBLIC)
                                                .addParameter(ParameterizedTypeName.of(Consumer.class, ClassName.of(packageName, "Cat")), "consumer")
                                                .beginMethodBody()
                                                .addStatement("consumer.accept(new $T())", ClassName.of(packageName, "Cat"))
                                                .end()
                                                .build())
                                        .addMethod(MethodSpec.builder(VOID, "play", Modifier.PUBLIC)
                                                .beginMethodBody()
                                                .addStatement("playWith($L)", ClassSpec.anonymousBuilder()
                                                        .setSuperClass(ParameterizedTypeName.of(Consumer.class, ClassName.of(packageName, "Cat")))
                                                        .addMethod(MethodSpec.builder(VOID, "accept", Modifier.PUBLIC)
                                                                .addParameter(ClassName.of(packageName, "Cat"), "cat")
                                                                .beginMethodBody()
                                                                .addStatement("System.out.println(cat.getSpeed())")
                                                                .end()
                                                                .build())
                                                        .build())
                                                .end()
                                                .build())
                                        .build()
                                , Modifier.PUBLIC, Modifier.STATIC)
                        .build()
        );

        // dog
        ProjectUtils.write(packageName,
                ClassSpec.builder("Dog", Modifier.PUBLIC)
                        .addAnnotation(AnnotationInstanceSpec.builder(ClassName.of(packageName, "Mammal")).build())
                        .setSuperClass(ClassName.of(packageName, "Animal"))
                        .addSuperInterfaces(ClassName.of(packageName, "Movable"), ClassName.of(packageName, "Audible"))
                        .addConstructor(ConstructorSpec.builder(Modifier.PUBLIC)
                                .beginConstructorBody()
                                .addStatement("super($S)", "DOG")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(INT, "getVolumeLevel", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return 25")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(INT, "getSpeed", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return 22")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(String.class, "getSoundType", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return $S", "woof")
                                .end()
                                .build())
                        .addMethod(MethodSpec.builder(String.class, "getMovementType", Modifier.PUBLIC)
                                .addAnnotation(Override.class)
                                .beginMethodBody()
                                .addStatement("return $S", "walk")
                                .end()
                                .build())
                        .build()
        );
    }
}
